package com.tp.server;

import com.tp.server.Registry;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Maneja las peticiones de un cliente
 * 
 * @author Franke Oviedo Triay
 * 
 * 
 */
public class ClientThread extends Thread {

	private Socket socket;

	private PrintWriter out;
	private BufferedReader in;
	private int sesion;
	private static final boolean AUTO_FLUSH = true;
	private static final String CLOSE = "exit";
	private Registry dns;
	private DomainCache domainCache;
	String inputLine = "";
	String outputLine = "";

	
	/**
	 * Constructor.
	 * @param socket
	 * @param sesion
	 * @param dns Nodo raiz de los registros dns.
	 * @param domainCache Mapa de registros en cache.
	 */
	public ClientThread(Socket socket, int sesion, Registry dns,
			DomainCache domainCache) {
		this.socket = socket;
		this.sesion = sesion;
		this.dns = dns;
		this.domainCache = domainCache;
		try {
			this.out = new PrintWriter(this.socket.getOutputStream(),
					AUTO_FLUSH);
			this.in = new BufferedReader(new InputStreamReader(
					this.socket.getInputStream()));
		} catch (IOException e) {
			System.out.println("Error: " + e.getMessage());
		}

	}

	@Override
	public void run() {
		try {
			while ((inputLine = in.readLine()) != null) {

				System.out.println("Cliente " + sesion + ": mensaje recibido: "+ inputLine);
				
				//formatea la entrada del cliente para eliminar de la cadena las subcadenas
				//"www" y ".ar" en caso que estas existan
				formatInput();
				
				//Intenta levantar el dominio pedido del cache.
				String cachedIp = domainCache.getDomainByKey(inputLine);
				
				//Si el dominio no es encontrado en cache es buscado en el arbol DNS.
				if (cachedIp == null) {
					Map<String, String> response = searchDomain(this.dns, inputLine);
					
					//se setea el resultado de la busqueda para cliente.
					outputLine = response.get("RESPONSE");
					
					//Si la busqueda fue exitosa se agrega la entrada al cache.
					if (!response.get("IP").isEmpty()) {
						
						domainCache.addDomain(response.get("DOMAIN"), response.get("IP"));
						
					}
					
				} else {
					//Si el resultado se encuntra en cache es devuelto sin realizar ninguna busqueda.
					outputLine = "Dominio cache: "+inputLine+" - A - "+cachedIp;
				}

				//Se envia el resultado al cliente.
				out.println(outputLine);
				
				//Si se entra el comando close se termina la ejecucion del hilo.
				if (inputLine.equalsIgnoreCase(CLOSE)) {
					break;
				}
			}
			out.close();
			in.close();
			socket.close();
		} catch (IOException e) {
			System.out.println("Error: " + e.getMessage());
		}

	}

	/**
	 * Formateo de entrada del cliente.
	 */
	private void formatInput() {
		//Evalua si la entrada contiene puntos para evitar errores durante el formateo
		if(inputLine.contains(".")){
			//Si la entrada comienza con www, este substring es eliminado de la misma
			if(inputLine.substring(0, inputLine.indexOf('.')).equalsIgnoreCase("www")){
				inputLine = inputLine.substring(inputLine.indexOf('.')+1, inputLine.length());
			}
			//Si la entrada finaliza con .ar, este substring es eliminado de la misma
			if(inputLine.substring(inputLine.lastIndexOf('.'), inputLine.length()).equalsIgnoreCase(".ar")){
				inputLine = inputLine.substring(0, inputLine.lastIndexOf('.'));
			}
		}
		
	}

	/**
	 * Busca un dominio en el arbol de registros DNS.
	 * @param reg Registro que sera analizado
	 * @param domain Dominio solicitado.
	 * @return Map<String, String> mapa con informacion del dominio solicitado:
	 * 		- RESPONSE: respuesta que se mostrarar al cliente.
	 * 		- IP: ip encontrada.
	 * 		- DOMAIN: dominio buscado.
	 */
	private Map<String, String> searchDomain(Registry reg, String domain) {

		//Separa la cadena ingresada por los puntos
		String[] domainParts = domain.split("\\.");
		
		//Buffer donde se guarda la respuesta
		StringBuffer response = new StringBuffer();
		
		//Mapa que se retornara con los resultados
		Map<String, String> result = new LinkedHashMap<String, String>();

		Registry r = reg;
		//Recorre las partes de la cadena ingresada desde el TLD.
		for (int i = domainParts.length - 1; i >= 0; i--) {
			String part = domainParts[i];
			//Busca la parte analizada en los hijos del registro
			r = r.getChildrenRegistries().get(part);
			
			//Si no existe el registro se retorna que el registro no fue encontrado.
			if (r == null) {
				result.put("RESPONSE", "Dominio no encontrado");
				result.put("IP", "");
				result.put("DOMAIN", domain);
				return result;
			}
		}

		//Si el registro encontrado es de tipo cname se realiza una busqueda recursiva con el valor del registro.
		if (r.getType().equals("cname")) {
			response.append(domain + " " + r.getType() + " of ");
			//Busqueda al partir del valor del registro cname
			Map<String, String> cnameResponse = searchDomain(reg, r.getValue());
			//Construccion del resultado en base a la busqueda realizada.
			response.append(cnameResponse.get("RESPONSE"));
			result.put("RESPONSE", response.toString());
			result.put("IP", cnameResponse.get("IP"));
			result.put("DOMAIN", domain);
			return result;
		}

		//Construccion del resultado para la busqueda realizada.
		response.append("Dominio: ");
		response.append(domain);
		response.append(" - " + r.getType() + " - ");
		response.append(r.getValue());

		result.put("RESPONSE", response.toString());
		result.put("IP", r.getValue());
		result.put("DOMAIN", domain);

		return result;

	}

	public Socket getSocket() {
		return socket;
	}

	public void setSocket(Socket socket) {
		this.socket = socket;
	}

	public int getSesion() {
		return this.sesion;
	}

	public void setSesion(int sesion) {
		this.sesion = sesion;
	}
}
